/*
 * FLINT PARTICLE SYSTEM
 * .....................
 * 
 * Author: Richard Lord (Big Room)
 * C# Port: Ben Baker (HeadSoft)
 * Copyright (c) Big Room Ventures Ltd. 2008
 * http://flintparticles.org
 * 
 * 
 * Licence Agreement
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Windows;

using FlintSharp.Actions;
using FlintSharp.Activities;
using FlintSharp.Counters;
using FlintSharp.Easing;
using FlintSharp.Emitters;
using FlintSharp.EnergyEasing;
using FlintSharp.Initializers;
using FlintSharp.Particles;
using FlintSharp.Zones;
using Action = FlintSharp.Actions.Action;

namespace FlintSharp.Emitters
{
    /// <summary>
    /// The Emitter public class manages the creation and ongoing state of particles. It uses a number of
    /// utility public classes to customise its behaviour.
    /// 
    /// <p>An emitter uses Initializers to customise the initial state of particles
    /// that it creates, their position, velocity, color etc. These are added to the 
    /// emitter using the addInitializer  method.</p>
    /// 
    /// <p>An emitter uses Actions to customise the behaviour of particles that
    /// it creates, to apply gravity, drag, fade etc. These are added to the emitter 
    /// using the addAction method.</p>
    /// 
    /// <p>An emitter uses Activities to customise its own behaviour in an ongoing manner, to 
    /// make it move or rotate.</p>
    /// 
    /// <p>An emitter uses a Counter to know when and how many particles to emit.</p>
    /// 
    /// <p>An emitter uses a Renderer to display the particles on screen.</p>
    /// 
    /// <p>All timings in the emitter are based on actual time passed, not on frames.</p>
    /// 
    /// <p>Most functionality is best added to an emitter using Actions,
    /// Initializers, Activities, Counters and Renderers. This offers greater 
    /// flexibility to combine behaviours witout needing to subpublic class 
    /// the Emitter itself.</p>
    /// 
    /// <p>The emitter also has position properties - x, y, rotation - that can be used to directly
    /// affect its location in the particle system.
    /// </summary>
    public class Emitter
    {
        protected static ParticleCreator m_creator = new ParticleCreator();
        protected IParticleFactory m_particleFactory;

        protected List<Initializer> m_initializers = null;
        protected List<Action> m_actions = null;
        protected List<Particle> m_particles = null;
        protected List<Activity> m_activities = null;
        protected ICounter m_counter = null;
        protected List<int> m_initializersPriority = null;
        protected List<int> m_actionsPriority = null;
        protected List<int> m_particlesPriority = null;
        protected List<int> m_activitiesPriority = null;

        protected float m_x = 0;
        protected float m_y = 0;
        protected float m_rotation = 0;
        protected bool m_spaceSort = false;
        protected bool m_finished = false;

        /// <summary>
        /// The constructor creates an emitter.
        /// </summary>
        public Emitter()
        {
            m_particleFactory = m_creator;
            m_particles = new List<Particle>();
            m_actions = new List<Action>();
            m_initializers = new List<Initializer>();
            m_activities = new List<Activity>();
            m_particlesPriority = new List<int>();
            m_actionsPriority = new List<int>();
            m_initializersPriority = new List<int>();
            m_activitiesPriority = new List<int>();
            m_counter = new ZeroCounter();
        }

        /// <summary>
        /// Adds an Initializer object to the Emitter. Initializers set the
        /// initial properties of particles created by the emitter.
        /// </summary>
        /// <param name="initializer"></param>
        public void AddInitializer(Initializer initializer)
        {
            int i;
            int priority = initializer.GetDefaultPriority();

            for (i = 0; i < m_initializersPriority.Count; i++)
                if (m_initializersPriority[i] < priority)
                    break;

            m_initializers.Insert(i, initializer);
            m_initializersPriority.Insert(i, priority);
            initializer.AddedToEmitter(this);
        }

        /// <summary>
        /// Adds an Initializer object to the Emitter. Initializers set the
        /// initial properties of particles created by the emitter.
        /// </summary>
        /// <param name="initializer">The Initializer to add</param>
        /// <param name="priority">Indicates the sequencing of the initializers. Higher numbers 
        /// cause an initializer to be run before other initialzers. All initializers 
        /// have a default priority which is used if no value is passed in this 
        /// parameter. The default priority is usually the one you want so this 
        /// parameter is only used when you need to override the default.</param>
        public void AddInitializer(Initializer initializer, int priority)
        {
            int i;

            for (i = 0; i < m_initializersPriority.Count; i++)
                if (m_initializersPriority[i] < priority)
                    break;

            m_initializers.Insert(i, initializer);
            m_initializersPriority.Insert(i, priority);
            initializer.AddedToEmitter(this);
        }

        /// <summary>
        /// Removes an Initializer from the Emitter.
        /// </summary>
        /// <param name="initializer">The Initializer to remove</param>
        public void RemoveInitializer(Initializer initializer)
        {
            for (int i = 0; i < m_initializers.Count; i++)
            {
                if (m_initializers[i] == initializer)
                {
                    m_initializers.RemoveAt(i);
                    m_initializersPriority.RemoveAt(i);
                    initializer.RemovedFromEmitter(this);
                    return;
                }
            }
        }

        /// <summary>
        /// Adds an Action to the Emitter. Actions set the behaviour
        /// of particles created by the emitter.
        /// </summary>
        /// <param name="action">The Action to add</param>
        public void AddAction(Action action)
        {
            int i;
            int priority = action.GetDefaultPriority();

            for (i = 0; i < m_actionsPriority.Count; i++)
                if (m_actionsPriority[i] < priority)
                    break;

            m_actions.Insert(i, action);
            m_actionsPriority.Insert(i, priority);
            action.AddedToEmitter(this);
        }

        /// <summary>
        /// Adds an Action to the Emitter. Actions set the behaviour
        /// of particles created by the emitter.
        /// </summary>
        /// <param name="action">The Action to add</param>
        /// <param name="priority">Indicates the sequencing of the actions. Higher numbers cause
        /// an action to be run before other actions. All actions have a default priority
        /// which is used if no value is passed in this parameter. The default priority is usually
        /// the one you want so this parameter is only used when you need to override the default.</param>
        public void AddAction(Action action, int priority)
        {
            int i;

            for (i = 0; i < m_actionsPriority.Count; i++)
                if (m_actionsPriority[i] < priority)
                    break;

            m_actions.Insert(i, action);
            m_actionsPriority.Insert(i, priority);
            action.AddedToEmitter(this);
        }

        /// <summary>
        /// Removes an Action from the Emitter.
        /// </summary>
        /// <param name="action">The Action to remove</param>
        public void RemoveAction(Action action)
        {
            for (int i = 0; i < m_actions.Count; i++)
            {
                if (m_actions[i] == action)
                {
                    m_actions.RemoveAt(i);
                    m_actionsPriority.RemoveAt(i);
                    action.RemovedFromEmitter(this);
                    return;
                }
            }
        }

        /// <summary>
        /// Adds an Activity to the Emitter. Activities set the behaviour
        /// of the Emitter.
        /// </summary>
        /// <param name="activity">The activity to add</param>
        public void AddActivity(Activity activity)
        {
            int i;
            int priority = activity.GetDefaultPriority();

            for (i = 0; i < m_activitiesPriority.Count; i++)
                if (m_activitiesPriority[i] < priority)
                    break;

            m_activities.Insert(i, activity);
            m_activitiesPriority.Insert(i, priority);
            activity.AddedToEmitter(this);
        }

        /// <summary>
        /// Adds an Activity to the Emitter. Activities set the behaviour
        /// of the Emitter.
        /// </summary>
        /// <param name="activity">The activity to add</param>
        /// <param name="priority">Indicates the sequencing of the activities. Higher numbers cause
        /// an activity to be run before other activities. All activities have a default priority
        /// which is used if no value is passed in this parameter. The default priority is usually
        /// the one you want so this parameter is only used when you need to override the default.</param>
        public void AddActivity(Activity activity, int priority)
        {
            int i;

            for (i = 0; i < m_activitiesPriority.Count; i++)
                if (m_activitiesPriority[i] < priority)
                    break;

            m_activities.Insert(i, activity);
            m_activitiesPriority.Insert(i, priority);
            activity.AddedToEmitter(this);
        }

        /// <summary>
        /// Removes an Activity from the Emitter.
        /// </summary>
        /// <param name="activity">The Activity to remove</param>
        public void RemoveActivity(Activity activity)
        {
            for (int i = 0; i < m_actions.Count; i++)
            {
                if (m_activities[i] == activity)
                {
                    m_activities.RemoveAt(i);
                    m_activitiesPriority.RemoveAt(i);
                    activity.RemovedFromEmitter(this);
                    return;
                }
            }
        }

        /// <summary>
        /// Used internally to create a particle.
        /// </summary>
        /// <returns></returns>
        protected Particle CreateParticle()
        {
            Particle particle = m_particleFactory.CreateParticle();
            int len = m_initializers.Count;

            particle.X = m_x;
            particle.Y = m_y;
            particle.Rotation = m_rotation;

            for (int i = 0; i < len; i++)
                m_initializers[i].Initialize(this, particle);

            m_particles.Insert(0, particle);

            return particle;
        }

        /// <summary>
        /// Starts the emitter. Until start is called, the emitter will not emit any particles.
        /// </summary>
        public void Start()
        {
            m_finished = false;

            uint len = (uint)m_activities.Count;

            for (int i = 0; i < len; i++)
                m_activities[i].Initialize(this);

            len = m_counter.StartEmitter(this);

            for (int i = 0; i < len; i++)
                CreateParticle();
        }

        /// <summary>
        /// Used internally and in derived public classes to update the emitter.
        /// </summary>
        /// <param name="g">Graphics object for drawing</param>
        /// <param name="elapsedTime">The duration, in seconds, of the current frame.</param>
        public void OnUpdateFrame(float elapsedTime)
        {
            int i;
            Particle particle;
            uint len = m_counter.UpdateEmitter(this, elapsedTime);

            for (i = 0; i < len; i++)
                CreateParticle();

            if (m_spaceSort)
                m_particles.Sort();

            len = (uint)m_activities.Count;

            for (i = 0; i < len; i++)
                m_activities[i].Update(this, elapsedTime);

            if (m_particles.Count > 0)
            {
                len = (uint)m_actions.Count;
                Action action;
                uint len2 = (uint)m_particles.Count;

                for (int j = 0; j < len; j++)
                {
                    action = m_actions[j];

                    for (i = 0; i < len2; i++)
                    {
                        particle = m_particles[i];
                        action.Update(this, particle, elapsedTime);
                    }
                }

                for (i = (int)len2 - 1; i >= 0; i--)
                {
                    particle = m_particles[i];

                    if (particle.IsDead)
                    {
                        m_particleFactory.DisposeParticle(particle);
                        m_particles.RemoveAt(i);
                    }
                }
            }
            else
                m_finished = true;
        }

        /// <summary>
        /// Clear emitter settings back to defaults
        /// </summary>
        public void ClearAll()
        {
            m_actions.Clear();
            m_initializers.Clear();
            m_activities.Clear();
            m_particlesPriority.Clear();
            m_actionsPriority.Clear();
            m_initializersPriority.Clear();
            m_activitiesPriority.Clear();
            m_particles.Clear();
        }

        /// <summary>
        /// Cleans up the emitter prior to removal. If you don't call this method,
        /// the garbage collector will clean up all the particles in teh usual way.
        /// If you use this method, the particles will be returned to the particle
        /// factory for reuse.
        /// </summary>
        public void Dispose()
        {
            int len = m_particles.Count;

            for (int i = 0; i < len; i++)
                m_particleFactory.DisposeParticle(m_particles[i]);

            m_particles.Clear();
        }

        /// <summary>
		/// Makes the emitter skip forwards a period of time with a single update.
		/// Used when you want the emitter to look like it's been running for a while. 
        /// </summary>
        /// <param name="time">The time, in seconds, to skip ahead.</param>
        public void RunAhead(float time)
        {
            float step = 1f / 30f; // 30 FPS

            while (time > 0)
            {
                time -= step;
                OnUpdateFrame(step);
            }
        }

        /// <summary>
        /// Indicates the x coordinate of the Emitter within the particle system's coordinate space.
        /// </summary>
        public float X
        {
            get { return m_x; }
            set { m_x = value; }
        }

        /// <summary>
        /// Indicates the y coordinate of the Emitter within the particle system's coordinate space.
        /// </summary>
        public float Y
        {
            get { return m_y; }
            set { m_y = value; }
        }

        /// <summary>
        /// Indicates the rotation of the Emitter, in degrees, within the particle system's coordinate space.
        /// </summary>
        public float Rotation
        {
            get { return (float)Utils.RadiansToDegrees(m_rotation); }
            set { m_rotation = (float)Utils.DegreesToRadians(value); }
        }

        /// <summary>
        /// Indicates the rotation of the Emitter, in radians, within the particle system's coordinate space.
        /// </summary>
        public float RotRadians
        {
            get { return m_rotation; }
            set { m_rotation = value; }
        }

        public bool SpaceSort
        {
            get { return m_spaceSort; }
            set { m_spaceSort = value; }
        }

        /// <summary>
        /// The array of all particles created by this emitter.
        /// </summary>
        public List<Particle> Particles
        {
            get { return m_particles; }
            set { m_particles = value; }
        }

        /// <summary>
        /// The Counter for the Emitter. The counter defines when and
        /// with what frequency the emitter emits particles.
        /// </summary>
        public ICounter Counter
        {
            get { return m_counter; }
            set { m_counter = value; }
        }

        public bool Finished
        {
            get { return m_finished; }
        }

        /// <summary>
        /// This is the particle factory used by the emitter to create and dispose of particles.
        /// The default value is an instance of the ParticleCreator public class that is shared by all
        /// emitters. You don't usually need to alter this unless you are not using the default
        /// particle type. Any custom particle factory should implement the ParticleFactory public class.
        /// </summary>
        public IParticleFactory ParticleFactory
        {
            get { return m_particleFactory; }
        }
    }
}
